Rust と C の FFI で生じるメモリ安全性の問題
from 項目34:FFI境界を通過するものを制御しよう
ほとんどのデータ構造は レジスタ に格納するには大きすぎるので、メモリ 上に保持する必要がある
データへのアクセスは メモリアドレス を通じて行う必要がある
C はメモリアドレスを ポインタ として表現する
ポインタはメモリアドレスを エンコード した数値
一方、Rust はメモリアドレスを 参照 として表現する
参照の数値表現は、生ポインタ として取り出すことができ、これを FFI 境界 に渡すことが可能
code:rs
extern "C" {
pub fn use_struct(v: *const FfiStruct) -> u32;
}
let v = FfiStruct {
byte: 1,
integer: 42
};
let x = unsafe { use_struct(&v as *const FfiStruct);
Rust の参照には 生存期間制約 があるが、生ポイント変換時には失われる(メモリ安全性 が保証されない)
そのため、以下のような危険性がある
ダングリング が起きる可能性がある
渡されたポインタの const を キャスト で無効化することで、Rust が イミュータブル としているデータが変更されるかもしれない
誤って ヒープ メモリを アロケーター に返却する(free())かもしれない
これにより、ダングリングが引き起こされる
上記の危険性は、FFI を用いて他の言語のコードを呼び出す際の 費用対効果 である
費用: メモリ安全性を失う
効果: 既存のコードを、対応する宣言を書く(自動生成する)だけで再利用できる
上記を緩和するために、「メモリの確保と解放がセットになるように、RAII パターンのラッパを用意しよう」
項目11:RAIIパターンにはDropトレイトを実装しよう
e.g.
code:c
/* FfiStruct を確保 */
FfiStruct* new_struct(uint32_t);
/* 確保した FfiStruct を解放 */
void free_struct(FfiStruct* s);
code:rs
extern "C" {
pub fn new_struct(v: u32) -> *mut FfiStruct;
pub fn free_struct(s: *mut FfiStruct);
}
struct FfiWrapper {
// 不変条件: inner は NULL ではない
inner: *mut FfiStruct
}
impl Drop for FfiWrapper {
fn drop(&mut self) {
// 安全性: free_struct は NULL ポインタでも動作するが、inner は NULL ではない
unsafe { free_struct(self.inner) }
}
}
このようにラッパ構造体で カプセル化 すると、Result を用いた安全なメソッドを提供できる
code:rs
type Error = String;
impl FfiWrapper {
pub fn new(val: u32) -> Result<Self, Error> {
let p: *mut FfiStruct = unsafe { new_struct(val) };
if p.is_null() {
Err("failed to get inner struct!".into())
else {
Ok(Self { inner: p })
}
}
pub fn set_byte(&mut self, b: u8) {
// 安全性: inner が NULL でないという不変条件に依存
let r: &mut FfiStruct = unsafe { &mut *self.inner };
r.byte = b;
}
}
内部のデータ構造を直接操作しても安全であれば、AsRef / AsMut トレイト を実装すると直接的な使用が可能となる
code:rs
impl AsMut<FfiStruct> for FfiWrapper {
fn as_mut(&mut self) -> &mut FfiStruct {
unsafe { &mut *self.inner }
}
}
wrapper.as_mut().byte = 12;
これを一般化すると、「unsafe な FFI ライブラリへのアクセスを safe な Rust コードにカプセル化しよう」となる
これにより、他の部分は unsafe なコードを書く必要がなくなる
また、危険なコードを集約することで、厳密に検査してテストする箇所が 1 箇所になり、問題の原因調査もやりやすい
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目